/*
* Copyright (C) 2011 Laurent Caillette
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.novelang.outfit.shell ;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Properties;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import org.novelang.logger.Logger;
import org.novelang.logger.LoggerFactory;
/**
* Uses <a href="http://java.sun.com/javase/6/docs/jdk/api/attach/spec/index.html" >Attach API</a>
* to shut JVMs down.
* It shuts down every local JVM started with {@value #SHUTDOWN_TATTOO_PROPERTYNAME} system
* property. The {@link ProcessShell#start(long, java.util.concurrent.TimeUnit)} method should
* take care of this.
*
* @author Laurent Caillette
*/
public class ShutdownTools
{
private static final Logger LOGGER = LoggerFactory.getLogger( ShutdownTools.class ) ;
private ShutdownTools() { }
public static final String SHUTDOWN_TATTOO_PROPERTYNAME = "org.novelang.outfit.shell.tattoo" ;
public static void shutdownAllTattooedVirtualMachines() {
shutdownAllTattooedVirtualMachines( AgentFileInstaller.getInstance().getJarFile() ) ;
}
private static void shutdownAllTattooedVirtualMachines( final File shutdownAgentJar ) {
final List< VirtualMachineDescriptor > descriptors = VirtualMachine.list() ;
for( final VirtualMachineDescriptor descriptor : descriptors ) {
try {
final VirtualMachine virtualMachine = VirtualMachine.attach( descriptor ) ;
try {
final Properties properties = virtualMachine.getSystemProperties() ;
if( properties.containsKey( SHUTDOWN_TATTOO_PROPERTYNAME ) ) {
shutdown( virtualMachine, shutdownAgentJar ) ;
}
} finally {
virtualMachine.detach() ;
}
} catch( AttachNotSupportedException e ) {
// Don't complain if the process already died.
if( ! e.getMessage().startsWith( "no such process" ) ) {
LOGGER.warn( "Counldn't attach, may be normal if other VM did shut down", e.getMessage() ) ;
}
} catch( IOException e ) {
// Don't complain if the process already died.
if( ! e.getMessage().startsWith( "no such process" ) ) {
LOGGER.warn( "Counldn't attach, may be normal if other VM did shut down", e.getMessage() ) ;
}
}
}
}
private static void shutdown( final VirtualMachine virtualMachine, final File shutdownAgentJar ) {
final String agentJarAbsolutePath = shutdownAgentJar.getAbsolutePath() ;
LOGGER.warn( "Forcing shutdown of Virtual Machine '", virtualMachine.id(), "' using '",
agentJarAbsolutePath, "'..." ) ;
try {
virtualMachine.loadAgent( agentJarAbsolutePath ) ;
} catch( Exception e ) {
LOGGER.warn( "Couldn't load agent on ", virtualMachine.id(),
" (maybe it's shutting down?): ", e.getMessage() ) ;
}
}
@SuppressWarnings( { "FieldCanBeLocal", "StaticNonFinalField" } )
private static boolean shutdownHookInstalled = false ;
private static final Object LOCK = new Object() ;
public static void installShutdownHookIfNeeded( final File shutdownAgentJar ) {
synchronized( LOCK ) {
if( !shutdownHookInstalled ) {
LOGGER.info( "Installing shutdown hook..." ) ;
final Thread thread = new Thread(
new Runnable() {
@Override
public void run() {
LOGGER.info( "Executing shutdown hook..." ) ;
shutdownAllTattooedVirtualMachines( shutdownAgentJar ) ;
}
},
"Shut all tattooed VMs down"
) ;
thread.setDaemon( true ) ;
Runtime.getRuntime().addShutdownHook( thread ) ;
shutdownHookInstalled = true ;
}
}
}
}